/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Simulator;

import java.io.FileNotFoundException;
import java.util.Stack;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 *
 * @author Scott
 */
public class SimulatorPassTwo
{

    private Stack DataStack;
    private Stack TestStack;
    private SimulatorPassOne program;
    private int Start;
    private int Current;
    private boolean Continue = true;
    private BufferedReader in;
    private String ExternalFile;
    public boolean Debug = false;

    public SimulatorPassTwo(SimulatorPassOne data, String file)
    {
        program = data;

        DataStack = new Stack();
        TestStack = new Stack();

        Start = Integer.valueOf(program.Records.get(0).StartAddress, 16);

        ExternalFile = file;

        if (!file.equals(""))
        {
            try
            {
                in = new BufferedReader(new InputStreamReader(new FileInputStream(file)));

            } catch (FileNotFoundException ex)
            {
                // Error handling here
                ex.printStackTrace();
            }
        }
    }

    public void Dump(int opt)
    {
        if (opt == 1)
        {
            DumpStacks();

        } else if (opt == 2)
        {
            DumpMemory();
        } else if (opt == 3)
        {
            DumpStacks();
            DumpMemory();
        } else
        {
            System.out.println("Invalid dump option");
        }
    }

    public void Run()
    {
        Current = Start;

        while (Continue)
        {
            ExecuteLine(program.Records.get(Current + 1));
            Current++;
        }
    }

    private void BeforeDebug()
    {
        System.out.println("-----Before simulation-----");
        System.out.print("LC=" + Current);
        System.out.print('\t');
        String code = Integer.toBinaryString(Integer.valueOf(program.Records.get(Current + 1).DataWord, 16));
        String fill = "";

        for (int i = 0; i < 16 - code.length(); i++)
        {
            fill += "0";
        }
        code = fill + code;

        Process current = DecodeLine(code);

        System.out.print("MEM(LC)=" + code);
        System.out.print('\t');
        System.out.print("op-code=" + code.substring(0, 2));
        System.out.print('\t');
        System.out.print("func=" + code.substring(2, 5));
        System.out.print('\t');
        System.out.println("s=" + code.substring(6));

        System.out.println("-->opc: " + current.Function + " -->func: " + current.Operator);

        System.out.println("values:" + '\t' + "S=" + Integer.valueOf(code.substring(6), 2) + '\t' + "MEM(S)=" + program.MEM[Integer.valueOf(code.substring(6), 2)] + 1);
        System.out.print("values:" + '\t' + "top of data-stack=");
        if (!DataStack.isEmpty())
        {
            System.out.println(DataStack.firstElement());
        } else
        {
            System.out.println("none");
        }

        System.out.print("values:" + '\t' + "top of test-stack=");
        if (!TestStack.isEmpty())
        {
            System.out.println(TestStack.firstElement());
        } else
        {
            System.out.println("none");
        }


        System.out.println("-----Before simulation-----");
        System.out.println();
    }

    private void AfterDebug()
    {

        System.out.println();
        System.out.println("=====After simulation=====");
        String code = Integer.toBinaryString(Integer.valueOf(program.Records.get(Current + 1).DataWord, 16));
        String fill = "";

        for (int i = 0; i < 16 - code.length(); i++)
        {
            fill += "0";
        }
        code = fill + code;

        System.out.println("values:" + '\t' + "S=" + Integer.valueOf(code.substring(6), 2) + '\t' + "MEM(S)=" + program.MEM[Integer.valueOf(code.substring(6), 2) + 1]);
        System.out.print("values:" + '\t' + "top of data-stack=");
        if (!DataStack.isEmpty())
        {
            System.out.println(DataStack.firstElement());
        } else
        {
            System.out.println("none");
        }

        System.out.print("values:" + '\t' + "top of test-stack=");
        if (!TestStack.isEmpty())
        {
            System.out.println(TestStack.firstElement());
        } else
        {
            System.out.println("none");
        }


        System.out.println("=====After simulation=====");
        System.out.println();
    }

    private void ExecuteLine(FileData line)
    {
        if (line.DataWord == null)
        {
            Continue = false;
        } else
        {
            String code = Integer.toBinaryString(Integer.valueOf(line.DataWord, 16));
            String fill = "";

            for (int i = 0; i < 16 - code.length(); i++)
            {
                fill += "0";
            }
            code = fill + code;

            //convert binary code back to instruction
            Process current = DecodeLine(code);

            if (Debug)
            {
                BeforeDebug();
            }

            System.out.println(line.DataWord);

            //System.out.print(current.Operator + "  " + current.Function + "  ");
            //System.out.println(Integer.valueOf(current.Operand, 2));

            if (current.Operator.equals("CNTL"))
            {
                ExecuteCntl(current);
            } else if (current.Operator.equals("STACK"))
            {
                ExecuteStack(current);
            } else if (current.Operator.equals("JUMP"))
            {
                ExecuteJump(current);
            } else if (current.Operator.equals("SOPER"))
            {
                ExecuteSoper(current);
            } else if (current.Operator.equals("MOPER"))
            {
                ExecuteMoper(current);
            }

            if (Debug)
            {
                AfterDebug();
            }
        }
    }

    private void DumpStacks()
    {

        System.out.println();
        System.out.print("data-stack:");
        if (!DataStack.isEmpty())
        {
            System.out.print(DataStack.firstElement());
            System.out.print('\t');
        } else
        {
            System.out.print("none");
            System.out.print('\t');
        }
        System.out.print("test-stack:");
        if (!TestStack.isEmpty())
        {
            System.out.println(TestStack.firstElement());
        } else
        {
            System.out.println("none");
        }
        System.out.println();
    }

    public void DumpMemory()
    {
        System.out.println();
        System.out.println("Memory Dump");
        System.out.println("-----------");
        for (int i = 0; i < 64; i++)
        {
            System.out.print(Integer.toHexString(i * 16));
            System.out.print('\t');
            for (int j = i * 16; j < (i * 16) + 16; j++)
            {
                if (program.MEM[j] != null)
                {
                    System.out.print(program.MEM[j]);
                } else
                {
                    System.out.print('-');
                }
                System.out.print('\t');
            }
            System.out.println();
        }
    }

    private Process DecodeLine(String code)
    {
        Process current = new Process();
        String op = code.substring(0, 2);
        String func = code.substring(2, 5);
        current.Operand = code.substring(6);
        if (op.equals("00"))
        {
            if (func.equals("000"))
            {
                current.Operator = "CNTL";
                current.Function = "HALT";
            } else if (func.equals("001"))
            {
                current.Operator = "CNTL";
                current.Function = "DUMP";
            } else if (func.equals("010"))
            {
                current.Operator = "CNTL";
                current.Function = "CLRD";
            } else if (func.equals("011"))
            {
                current.Operator = "CNTL";
                current.Function = "CLRT";
            } else if (func.equals("100"))
            {
                current.Operator = "CNTL";
                current.Function = "GOTO";
            } else if (func.equals("101"))
            {
                current.Operator = "STACK";
                current.Function = "PUSH";
                current.LitFlag = code.charAt(5);
            } else if (func.equals("110"))
            {
                current.Operator = "STACK";
                current.Function = "POP";
                current.LitFlag = code.charAt(5);
            } else if (func.equals("111"))
            {
                current.Operator = "STACK";
                current.Function = "TEST";
                current.LitFlag = code.charAt(5);
            }
        } else if (op.equals("01"))
        {
            current.Operator = "JUMP";

            if (func.equals("000"))
            {
                current.Function = "=";
            } else if (func.equals("001"))
            {
                current.Function = "^=";
            } else if (func.equals("010"))
            {
                current.Function = "<";
            } else if (func.equals("011"))
            {
                current.Function = ">";
            } else if (func.equals("100"))
            {
                current.Function = "<=";
            } else if (func.equals("101"))
            {
                current.Function = ">=";
            } else if (func.equals("110"))
            {
                current.Function = "tnull";
            } else if (func.equals("111"))
            {
                current.Function = "dnull";
            }
        } else if (op.equals("10"))
        {
            current.Operator = "SOPER";

            if (func.equals("000"))
            {
                current.Function = "ADD";
            } else if (func.equals("001"))
            {
                current.Function = "SUB";
            } else if (func.equals("010"))
            {
                current.Function = "MUL";
            } else if (func.equals("011"))
            {
                current.Function = "DIV";
            } else if (func.equals("100"))
            {
                current.Function = "OR";
            } else if (func.equals("101"))
            {
                current.Function = "AND";
            } else if (func.equals("110"))
            {
                if (code.charAt(5) == '0')
                {
                    current.Function = "READN";
                } else if (code.charAt(5) == '1')
                {
                    current.Function = "READC";
                }
            } else if (func.equals("111"))
            {
                if (code.charAt(5) == '0')
                {
                    current.Function = "WRITEN";
                } else if (code.charAt(5) == '1')
                {
                    current.Function = "WRITEC";
                }
            }
        } else if (op.equals("11"))
        {
            current.Operator = "MOPER";

            if (func.equals("000"))
            {
                current.Function = "ADD";
            } else if (func.equals("001"))
            {
                current.Function = "SUB";
            } else if (func.equals("010"))
            {
                current.Function = "MUL";
            } else if (func.equals("011"))
            {
                current.Function = "DIV";
            } else if (func.equals("100"))
            {
                current.Function = "OR";
            } else if (func.equals("101"))
            {
                current.Function = "AND";
            } else if (func.equals("110"))
            {
                if (code.charAt(5) == '0')
                {
                    current.Function = "READN";
                } else if (code.charAt(5) == '1')
                {
                    current.Function = "READC";
                }
            } else if (func.equals("111"))
            {
                if (code.charAt(5) == '0')
                {
                    current.Function = "WRITEN";
                } else if (code.charAt(5) == '1')
                {
                    current.Function = "WRITEC";
                }
            }
        }

        return current;
    }

    private void ExecuteCntl(Process current)
    {
        if (current.Function.equals("HALT"))
        {
            Continue = false;
        } else if (current.Function.equals("DUMP"))
        {
            Dump(Integer.valueOf(current.Operand, 2));
        } else if (current.Function.equals("CLRD"))
        {
            DataStack.clear();
        } else if (current.Function.equals("CLRT"))
        {
            TestStack.clear();
        } else if (current.Function.equals("GOTO"))
        {
            Current = Integer.valueOf(current.Operand, 2) + 1;
        }
    }

    private void ExecuteStack(Process current)
    {
        if (current.Function.equals("PUSH"))
        {
            if(current.LitFlag == '0')
            {
            DataStack.push(program.MEM[Integer.valueOf(current.Operand, 2)]);
            }
            else
            {
                DataStack.push(Integer.toHexString(Integer.valueOf(current.Operand, 2)));
            }
        } else if (current.Function.equals("POP"))
        {
            program.MEM[Integer.valueOf(current.Operand, 2)] = (String) DataStack.pop();
        } else if (current.Function.equals("TEST"))
        {
            if (!DataStack.isEmpty())
            {
                Integer result = Integer.valueOf((String) DataStack.pop());

                if (Integer.valueOf(current.Operand, 2) == result)
                {
                    TestStack.push(0);
                } else if (Integer.valueOf(current.Operand, 2) < result)
                {
                    TestStack.push(1);
                } else if (Integer.valueOf(current.Operand, 2) > result)
                {
                    TestStack.push(2);
                } else if (Integer.valueOf(current.Operand, 2) <= result)
                {
                    TestStack.push(3);
                } else if (Integer.valueOf(current.Operand, 2) >= result)
                {
                    TestStack.push(4);
                }
                DataStack.push(result.toString());
            } else
            {
                System.out.println("Empty stack error at: " + Current);
            }
        }
    }

    private void ExecuteJump(Process current)
    {
        if (current.Function.equals("="))
        {
            Integer result = (Integer) TestStack.pop();

            if (result == 0)
            {
                Current = Integer.valueOf(current.Operand, 2) + 1;
            }
        } else if (current.Function.equals("<"))
        {
            Integer result = (Integer) TestStack.pop();

            if (result == 1)
            {
                Current = Integer.valueOf(current.Operand, 2) + 1;
            }
        } else if (current.Function.equals(">"))
        {
            Integer result = (Integer) TestStack.pop();

            if (result == 2)
            {
                Current = Integer.valueOf(current.Operand, 2) + 1;
            }
        } else if (current.Function.equals("<="))
        {
            Integer result = (Integer) TestStack.pop();

            if (result == 3)
            {
                Current = Integer.valueOf(current.Operand, 2) + 1;
            }
        } else if (current.Function.equals(">="))
        {
            Integer result = (Integer) TestStack.pop();

            if (result == 4)
            {
                Current = Integer.valueOf(current.Operand, 2) + 1;
            }
        } else if (current.Function.equals("tnull"))
        {
            if (TestStack.isEmpty())
            {
                Current = Integer.valueOf(current.Operand, 2) + 1;
            }
        } else if (current.Function.equals("dnull"))
        {
            if (DataStack.isEmpty())
            {
                Current = Integer.valueOf(current.Operand, 2) + 1;
            }
        }
    }

    private void ExecuteSoper(Process current)
    {
        if (current.Function.equals("ADD"))
        {
            Integer result = 0;

            for (int i = 0; i < Integer.valueOf(current.Operand, 2); i++)
            {
                Integer next = Integer.valueOf((String) DataStack.pop(), 16);
                if (i == 0)
                {
                    result = next;
                } else if ((result + next) <= 32767)
                {
                    result += next;
                } else
                {
                    //overflow error
                    System.out.println("Overflow error at :" + Current);
                }
            }
            DataStack.push(Integer.toHexString(result));
        } else if (current.Function.equals("SUB"))
        {
            Integer result = 0;
            for (int i = 0; i < Integer.valueOf(current.Operand, 2); i++)
            {
                Integer next = Integer.valueOf((String) DataStack.pop(), 16);
                if (i == 0)
                {
                    result = next;
                } else if ((result - next) >= -32768)
                {
                    result -= next;
                } else
                {
                    //overflow error
                    System.out.println("Overflow error");
                }
            }
            DataStack.push(Integer.toHexString(result));
        } else if (current.Function.equals("MUL"))
        {
            Integer result = 0;
            for (int i = 0; i < Integer.valueOf(current.Operand, 2); i++)
            {
                Integer next = Integer.valueOf((String) DataStack.pop(), 16);
                if (i == 0)
                {
                    result = next;
                } else if ((result * next) <= 32767)
                {
                    result *= next;
                } else
                {
                    //overflow error
                    System.out.println("Overflow error");
                }
            }
            DataStack.push(Integer.toHexString(result));
        } else if (current.Function.equals("DIV"))
        {
            Integer result = 0;
            try
            {
                for (int i = 0; i < Integer.valueOf(current.Operand, 2); i++)
                {
                    Integer next = Integer.valueOf((String) DataStack.pop(), 16);
                    if (i == 0)
                    {
                        result = next;
                    } else if (next != 0)
                    {
                        result /= next;
                    } else
                    {
                        //div by 0 error
                        System.out.println("Divide by zero error");
                    }
                }
            }
            catch (NumberFormatException ex)
            {
                System.out.println("Error: Trying to remove from stack when the stack is empty.");
            }
            DataStack.push(Integer.toHexString(result));
        } else if (current.Function.equals("OR"))
        {
            Integer result = 0;
            for (int i = 0; i < Integer.valueOf(current.Operand, 2); i++)
            {
                Integer next = Integer.valueOf((String) DataStack.pop(), 16);
                if (i == 0)
                {
                    result = next;
                } else
                {
                    result = result | next;
                }

            }
            DataStack.push(Integer.toHexString(result));

        } else if (current.Function.equals("AND"))
        {
            Integer result = 0;
            for (int i = 0; i < Integer.valueOf(current.Operand, 2); i++)
            {
                Integer next = Integer.valueOf((String) DataStack.pop(), 16);
                if (i == 0)
                {
                    result = next;
                } else
                {
                    result = result & next;
                }
            }
            DataStack.push(Integer.toHexString(result));

        } else if (current.Function.equals("WRITEN") || current.Function.equals("WRITEC"))
        {
            for (int i = 0; i < Integer.valueOf(current.Operand, 2); i++)
            {
                String item = (String) DataStack.pop();
                String fill = "";
                for (int j = 0; j < 4 - item.length(); j++)
                {
                    fill += '0';
                }
                System.out.println(fill + item);
            }
        } else if (current.Function.equals("READN") || current.Function.equals("READC"))
        {
            if (!ExternalFile.equals(""))
            {
                for (int i = 0; i < Integer.valueOf(current.Operand, 2); i++)
                {
                    String result = "";
                    try
                    {
                        result += (char) in.read();
                        DataStack.push(result);
                    } catch (IOException ex)
                    {
                        ex.printStackTrace();
                    }
                }
            } else
            {
                //no file found
                System.out.println("File not successfully read/found");
            }
        }
    }

    private void ExecuteMoper(Process current)
    {
        if (current.Function.equals("ADD"))
        {
            Integer mem = Integer.valueOf(program.MEM[Integer.valueOf(current.Operand, 2)], 16);
            Integer stack = Integer.valueOf((String) DataStack.pop(), 16);
            if (mem + stack <= 32767)
            {
                stack += mem;
                DataStack.push(Integer.toHexString(stack));
            } else
            {
                //overflow
                System.out.println("Overflow error");
                DataStack.push(Integer.toHexString(stack));
            }
        } else if (current.Function.equals("SUB"))
        {
            Integer mem = Integer.valueOf(program.MEM[Integer.valueOf(current.Operand, 2)], 16);
            Integer stack = Integer.valueOf((String) DataStack.pop(), 16);
            if (mem - stack >= -32768)
            {
                stack -= mem;
                DataStack.push(Integer.toHexString(stack));
            } else
            {
                //overflow
                System.out.println("Overflow error");
                DataStack.push(Integer.toHexString(stack));
            }
        } else if (current.Function.equals("MUL"))
        {
            Integer mem = Integer.valueOf(program.MEM[Integer.valueOf(current.Operand, 2)], 16);
            Integer stack = Integer.valueOf((String) DataStack.pop(), 16);
            if (mem * stack <= 32767)
            {
                stack *= mem;
                DataStack.push(Integer.toHexString(stack));
            } else
            {
                //overflow
                System.out.println("Overflow error");
                DataStack.push(Integer.toHexString(stack));
            }
        } else if (current.Function.equals("DIV"))
        {
            Integer mem = Integer.valueOf(program.MEM[Integer.valueOf(current.Operand, 2)], 16);
            Integer stack = Integer.valueOf((String) DataStack.pop(), 16);
            if (mem != 0)
            {
                stack /= mem;
                DataStack.push(Integer.toHexString(stack));
            } else
            {
                //overflow
                System.out.println("Divide by zero error");
                DataStack.push(Integer.toHexString(stack));
            }
        } else if (current.Function.equals("OR"))
        {
            Integer mem = Integer.valueOf(program.MEM[Integer.valueOf(current.Operand, 2)], 16);
            Integer stack = Integer.valueOf((String) DataStack.pop(), 16);

            stack = stack | mem;
            DataStack.push(Integer.toHexString(stack));

        } else if (current.Function.equals("AND"))
        {
            Integer mem = Integer.valueOf(program.MEM[Integer.valueOf(current.Operand, 2)], 16);
            Integer stack = Integer.valueOf((String) DataStack.pop(), 16);
            stack = stack & mem;
            DataStack.push(Integer.toHexString(stack));

        } else if (current.Function.equals("WRITEN") || current.Function.equals("WRITEC"))
        {
            String fill = "";
            for (int i = 0; i < 4 - program.MEM[Integer.valueOf(current.Operand, 2)].length(); i++)
            {
                fill += "0";
            }

            System.out.println(fill + program.MEM[Integer.valueOf(current.Operand, 2)]);
        } else if (current.Function.equals("READN") || current.Function.equals("READC"))
        {
            if (!ExternalFile.equals(""))
            {

                String result = "";
                try
                {
                    result += (char) in.read();
                    program.MEM[Integer.valueOf(current.Operand, 2)] = result;
                } catch (IOException ex)
                {
                    ex.printStackTrace();
                }
            } else
            {
                //no file found
                System.out.println("File not successfully read/found");
            }
        }
    }
}
